%% Author: sdodd
%% Created: Jun 26, 2008
%% Description: Crackling Fire brute force puzzle solving application.
-module(cfire).

%%
%% Include files
%%

-import(lists, [foreach/2]).

%%
%% Exported Functions
%%
-export([start/0]).

%%
%% API Functions
%%

start() -> 
    Twists = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    Puzzle = {
              {0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},
              {0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},
              {0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0}
              },
    rotate(1, Twists, Puzzle),
    ok.

%%
%% Local Functions
%%

%% Tries all the possible rotations at the given depth and below and prints message if solution found.
rotate(Depth, Twists, _Puzzle) when Depth > tuple_size(Twists) -> ok;
rotate(Depth, Twists, Puzzle) ->
	foreach( 
      fun(Count) ->
          Twists2 = setelement(Depth, Twists, Count),
          {Puzzle2, _Abandon} = project(Depth, Twists2, Puzzle),	% TODO  abandon
          print_progress(Depth, Twists2),	% for debugging only
          rotate( Depth+1, Twists2, Puzzle2 ) 
      end, 
      [1,2,3,4] ).

%% Projects the given twists onto the puzzle and prints message if solution found.
project(Depth, Twists, Puzzle) ->
	project(1, 1, 1, Depth, Twists, Puzzle).

project(IndexP, _IndexT, _Orientation, _Depth, Twists, Puzzle) when IndexP > tuple_size(Puzzle)-1 -> 
    print_puzzle(Puzzle, Twists),
    {Puzzle, ok};
project(IndexP, IndexT, Orientation, _Depth, Twists, Puzzle) ->
	{IndexT2, Orientation2} = orient(IndexP, IndexT, Orientation, Twists),
    NewLocation = vadd(element(IndexP, Puzzle), units(Orientation2+1)),
    Puzzle2 = setelement(IndexP+1, Puzzle, NewLocation),
    % TODO  support "starting" and "abandon", change _Depth to Depth
    case not_proximate(NewLocation) 
         orelse IndexP > 2
         andalso has_collision(IndexP-1, NewLocation, Puzzle2) of
        true -> {Puzzle2, ok};	% "abandon" goes here
    	false -> project(IndexP+1, IndexT2, Orientation2, _Depth, Twists, Puzzle2)
    end.

orient(IndexP, IndexT, Orientation, Twists) ->  
    case kink_map(IndexP) of
        0 -> {IndexT, Orientation};
        1 -> {IndexT+1, rotations(Orientation+1, element(IndexT, Twists))}
	end.

not_proximate({X, Y, Z}) -> abs(X-1)>1 orelse abs(Y-1)>1 orelse abs(Z-1)>1.

has_collision(0, _, _) -> false;
has_collision(Offset, Location, Puzzle) ->
    case Location == element(Offset, Puzzle) of
        true -> true;
        false -> has_collision(Offset-1, Location, Puzzle)
    end.

vadd({X1,Y1,Z1}, {X2,Y2,Z2}) -> {X1+X2,Y1+Y2,Z1+Z2}.

print_puzzle(Puzzle) -> io:format("~w~n", [Puzzle]).
print_puzzle(Puzzle, Twists) -> io:format("~w!~n", [Twists]), print_puzzle(Puzzle).

print_progress(Depth, _Twists) when Depth==5 -> io:format("~w ",[Depth]);
print_progress(_, _) -> [].

kink_index(N) ->
    element(N,{2,3,4,6,7,9,10,11,13,15,16,17,18,20,22,24}).
kink_map(N) ->
    element(N,{0,0,1,1,1,0,1,1,0,1,1,1,0,1,0,1,1,1,1,0,1,0,1,0,1,0,0}).
units(N) ->					% the unit vectors
    element(N,{
             {1,0,0},  % 0
             {0,1,0},  % 1
             {0,0,1},  % 2
             {-1,0,0}, % 3
             {0,-1,0}, % 4
             {0,0,-1}  % 5
            }).
rotations(N) ->				% rotations of the unit vectors
    element(N,{
             {2,1,5,4},
             {0,2,3,5},
             {1,0,4,3},
             {5,4,2,1},
             {3,5,0,2},
             {4,3,1,0}
            }).
rotations(N,M) -> 
    element(M, rotations(N)).